package com.jse.api;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import com.jse.Strings;

public class Ali {

	private static final Object LOCK = new Object();
	
	public static String sign(Map<String, String> map) {
		StringBuffer content = new StringBuffer();
		var sortMap = new TreeMap<String, String>(map);
		List<String> keys = new ArrayList<String>(sortMap.keySet());
		Collections.sort(keys);
		int index = 0;
		for (int i = 0; i < keys.size(); i++) {
			String key = keys.get(i);
			String value = map.get(key);
			if (Strings.isNotEmpty(key) && Strings.isNotEmpty(value)) {
				content.append((index == 0 ? "" : "&") + key + "=" + value);
				index++;
			}
		}
		return content.toString();
	}
	
	public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream in) throws Exception {
		if (in == null || Strings.isEmpty(algorithm)) {
			return null;
		}
		KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
		byte[] encodedKey = in.readAllBytes();
		encodedKey = Base64.getDecoder().decode(encodedKey);
		return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
	}

	public static String rsaSign(String content, String privateKey, String charset,String signType) throws RuntimeException {
		try {
			PrivateKey priKey = getPrivateKeyFromPKCS8("RSA", new ByteArrayInputStream(privateKey.getBytes()));
			java.security.Signature signature = java.security.Signature.getInstance(signType.equals("RSA2")?"SHA256WithRSA":"SHA1WithRSA");
			signature.initSign(priKey);
			if (Strings.isEmpty(charset))signature.update(content.getBytes());
			else signature.update(content.getBytes(charset));
			return new String(Base64.getEncoder().encode(signature.sign()));
		} catch (InvalidKeySpecException ie) {
			throw new RuntimeException("RSA私钥格式不正确，请检查是否正确配置了PKCS8格式的私钥", ie);
		} catch (Exception e) {
			throw new RuntimeException("RSAcontent = " + content + "; enc = " + charset, e);
		}
	}
	public static String hmacSHA1Signature(String key, String data) {
        try {
            byte[] signData = sign(key.getBytes("UTF-8"), data.getBytes("UTF-8"), null,
                                LOCK, "HmacSHA1");
            return new String(Base64.getEncoder().encode(signData));
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException("Unsupported algorithm: " + "UTF-8", ex);
        }
    }
	
	public static byte[] sign(byte[] key, byte[] data, Mac macInstance, Object lock, String algorithm) {
        try {
            if (macInstance == null) {
                synchronized (lock) {
                    if (macInstance == null) {
                        macInstance = Mac.getInstance(algorithm);
                    }
                }
            }
            Mac mac;
            try {
                mac = (Mac) macInstance.clone();
            } catch (CloneNotSupportedException e) {
                mac = Mac.getInstance(algorithm);
            }
            mac.init(new SecretKeySpec(key, algorithm));
            return mac.doFinal(data);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException("Unsupported algorithm: " + algorithm, ex);
        } catch (InvalidKeyException ex) {
            throw new RuntimeException("Invalid key: " + key, ex);
        }
    }
	
	public static boolean rsaCheckV1(Map<String, String> params, String publicKey,
            String charset,String signType) throws RuntimeException {
		String sign = params.get("sign");
		String content = getSignCheckContentV1(params);
		
		return rsaCheck(content, sign, publicKey, charset,signType);
    }
	
	public static boolean rsaCheckV1(Map<String, String> params, String publicKey,
            String charset) throws RuntimeException {
	String sign = params.get("sign");
	String content = getSignCheckContentV1(params);
	
	return rsaCheckContent(content, sign, publicKey, charset);
	}
	
	public static boolean rsaCheck(String content, String sign, String publicKey, String charset,
            String signType) throws RuntimeException {

if ("RSA".equals(signType)) {

return rsaCheckContent(content, sign, publicKey, charset);

} else if ("RSA2".equals(signType)) {

return rsa256CheckContent(content, sign, publicKey, charset);

} else {

throw new RuntimeException("Sign Type is Not Support : signType=" + signType);
}

}
	
	public static boolean rsa256CheckContent(String content, String sign, String publicKey,
            String charset) throws RuntimeException {
try {
PublicKey pubKey = getPublicKeyFromX509("RSA",
new ByteArrayInputStream(publicKey.getBytes()));

java.security.Signature signature = java.security.Signature
.getInstance("SHA256WithRSA");

signature.initVerify(pubKey);

if (Strings.isEmpty(charset)) {
signature.update(content.getBytes());
} else {
signature.update(content.getBytes(charset));
}

return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
} catch (Exception e) {
throw new RuntimeException(
"RSAcontent = " + content + ",sign=" + sign + ",enc = " + charset, e);
}
}
	
	public static boolean rsaCheckContent(String content, String sign, String publicKey,
            String charset) throws RuntimeException {
try {
PublicKey pubKey = getPublicKeyFromX509("RSA",
new ByteArrayInputStream(publicKey.getBytes()));

java.security.Signature signature = java.security.Signature
.getInstance("SHA1WithRSA");

signature.initVerify(pubKey);

if (Strings.isEmpty(charset)) {
signature.update(content.getBytes());
} else {
signature.update(content.getBytes(charset));
}

return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
} catch (Exception e) {
throw new RuntimeException(
"RSAcontent = " + content + ",sign=" + sign + ",enc = " + charset, e);
}
}
	
	public static PublicKey getPublicKeyFromX509(String algorithm,
            InputStream ins) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

StringWriter writer = new StringWriter();
InputStreamReader reader=new InputStreamReader(ins);
char[] buffer = new char[8192 >> 1];
int amount;
while ((amount = reader.read(buffer)) >= 0) {
writer.write(buffer, 0, amount);
}
byte[] encodedKey = writer.toString().getBytes();

encodedKey = Base64.getDecoder().decode(encodedKey);

return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
}
	
	public static String getSignCheckContentV1(Map<String, String> params) {
        if (params == null) {
            return null;
        }

        params.remove("sign");
        params.remove("sign_type");

        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            content.append((i == 0 ? "" : "&") + key + "=" + value);
        }

        return content.toString();
    }
}
